/*
 * Copyright 2007-2008  Lukasz Laszko lukaszlaszko@gmail.com
 * 
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and this
 * permission notice and warranty disclaimer appear in supporting
 * documentation, and that the name of the author not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 * 
 * The author disclaim all warranties with regard to this
 * software, including all implied warranties of merchantability
 * and fitness.  In no event shall the author be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether
 * in an action of contract, negligence or other tortious action,
 * arising out of or in connection with the use or performance of
 * this software. 
 */
package org.pg.biomedics.asr.mpeg7;

import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.Arrays;
import java.util.GregorianCalendar;

import edu.cmu.sphinx.result.Result;
import edu.cmu.sphinx.result.Lattice;
import edu.cmu.sphinx.result.LatticeOptimizer;
import edu.cmu.sphinx.result.Node;
import edu.cmu.sphinx.result.Edge;

import org.pg.biomedics.asr.mpeg7.code.LinguisticUnits;
import org.pg.biomedics.asr.mpeg7.code.TokenRepresentations;
import org.pg.biomedics.asr.mpeg7.code.BlockAudioTypes;

/**
 * MPEG-7 document and document parts factory
 * @author Lukasz Laszko
 */
public class DocumentFactory 
{
    // <editor-fold desc="Methods">
    
    /**
     * Creates MPEG-7 compilant description of ASR process basing on Sphinx-4 result
     * @param result Sphinx-4 result tree
     * @return MPEG-7 SCD document
     * @throws org.pg.biomedics.asr.mpeg7.DocumentFactoryException
     */
    public SpokenContentDocument createDocument(Result result) throws DocumentFactoryException
    {
        // validate input params
        if (!result.isFinal())
            throw new DocumentFactoryException("Can't create SpokenContentDocument, result must be final");
                       
        // create spoken content header
        WordLexicon wordLexicon = createWordLexicon(result);
        
        SpokenContentHeader spokenContentHeader = new SpokenContentHeader();
        spokenContentHeader.setWordLexicon(wordLexicon);
        
        // create spoken content lattice
        Block block = createBlock(result, wordLexicon, 0, BlockAudioTypes.SPEECH, null); 
        
        SpokenContentLattice spokenContentLattice = new SpokenContentLattice();
        spokenContentLattice.getBlocks().add(block);
        
        // create document
        SpokenContentDocument spokenContentDocument = new SpokenContentDocument();
        spokenContentDocument.setSpokenContentHeader(spokenContentHeader);
        spokenContentDocument.setSpokenContentLattice(spokenContentLattice);
        
        return spokenContentDocument;
    }
    
    /**
     * Gets word lexicon
     * @param result
     * @return list of words
     * @throws org.pg.biomedics.asr.mpeg7.DocumentFactoryException
     */
    public WordLexicon createWordLexicon(Result result) throws DocumentFactoryException
    {
        // input params validation
        if (result == null)
            throw new DocumentFactoryException("Can't create word lexicon, result can't be null");
            
        Lattice lattice = new Lattice(result);        
        WordLexicon wordLexicon = new WordLexicon();                 
        // TODO : set wordLexicon.setNumOfOriginalEntries(numOfOriginalEntries) from dictionary size
        
        Collection nodes = lattice.getNodes();
        for (Iterator iterator = nodes.iterator(); iterator.hasNext();)
        {
            Node node = (Node)iterator.next();
            String word = node.getWord().toString();
            
            if (wordLexicon.getWordIndex(word) == -1)
            {
                WordToken wordToken = new WordToken();
                wordToken.setWord(word);
                if ((word.indexOf("<") > -1) || (word.indexOf(">") > -1) || (word.indexOf("/") > -1)) //check if word is non speech
                    wordToken.setLinguisticUnit(LinguisticUnits.NONSPEECH);
                else
                    wordToken.setLinguisticUnit(LinguisticUnits.WORD);

                wordLexicon.getTokens().add(wordToken);
            }
        }               
        
        return wordLexicon;
    }
    
    /**
     * Creates a new lattice block. Block nodes contanins references to words in word lexicon
     * @param result ASR result 
     * @param wordLexicon word lexicon
     * @param blockIndex block index
     * @param blockAudioType block audio code
     * @param defaultSpeakerInfoRef
     * @return lattice block
     * @throws org.pg.biomedics.asr.mpeg7.DocumentFactoryException
     */
    public Block createBlock(Result result, WordLexicon wordLexicon, int blockIndex, String blockAudioType, String defaultSpeakerInfoRef) 
            throws DocumentFactoryException
    {
        // input params validation
        if (result == null)
            throw new DocumentFactoryException("Can't create block, result can't be null");
        else if (wordLexicon == null)
            throw new DocumentFactoryException("Can't create block, word lexicon can't be null");
        
        Block block = new Block();      
        
        MediaTime mediaTime = new MediaTime();
        mediaTime.setMediaTimePoint(new GregorianCalendar());       
        block.setMediaTime(mediaTime);
        block.setNum(blockIndex);
        block.setAudio(blockAudioType);
        block.setDefaultSpeakerInfoRef(defaultSpeakerInfoRef);
        
        Lattice lattice = new Lattice(result);
        List<LinkedList<Node>> paths = allPathsFrom(new LinkedList<Node>(), lattice.getInitialNode());
        List<LinkedList<Node>> nodes = getPathNodes(paths);
        for (int i = 0; i < nodes.size(); i++)
        {
            // create MPEG-7 node 
            org.pg.biomedics.asr.mpeg7.Node mpegNode = new org.pg.biomedics.asr.mpeg7.Node();
            mpegNode.setNum(i);
            
            int timeOffset = 0;
            for (int j = 0; j < nodes.get(i).size(); j++)
            {
                Node node = nodes.get(i).get(j);
                timeOffset = node.getBeginTime();
                
                WordLink wordLink = new WordLink();
                wordLink.setNodeOffset(j);
                wordLink.setProbability(node.getPosterior());
                wordLink.setAcousticScore(node.getBackwardScore());
                wordLink.setWord(wordLexicon.getWordIndex(node.getWord().toString()));
                
                mpegNode.getWordLinks().add(wordLink);
            }
            
            mpegNode.setTimeOffset(timeOffset);
            block.getNodes().add(mpegNode);
        }
        
        return block;
    }   
    
    // </editor-fold>
    
    //<editor-fold desc="Support methods">
    
    private List<LinkedList<Node>> allPathsFrom(LinkedList<Node> path, Node n) 
    {
        LinkedList<Node> newPath = new LinkedList<Node>();
        newPath.addAll(path);
        newPath.add(n);
        
        List<LinkedList<Node>> l = new LinkedList<LinkedList<Node>>();
        if (isTerminalNode(n)) 
        {
            l.add(path);
        } 
        else 
        {
            for (Iterator i = n.getLeavingEdges().iterator(); i.hasNext();) 
            {
                Edge e = (Edge) i.next();
                l.addAll(allPathsFrom(newPath, e.getToNode()));
            }
        }
        return l; 
    }

    private boolean isTerminalNode(Node node)
    {
        if (node.getLeavingEdges() == null)
            return true;
        
        Iterator iterator = node.getLeavingEdges().iterator();
        return (!iterator.hasNext());
    }
    
    private List<LinkedList<Node>> getPathNodes(List<LinkedList<Node>> paths)
    {
        // create nodes path
        LinkedList<LinkedList<Node>> nodesPath = new LinkedList<LinkedList<Node>>();
        for (int i = 0; i < paths.size(); i++)
        {
            for (int j = 0; j < paths.get(i).size(); j++)
            {
                // create equal nodes container
                if (nodesPath.size() == j)
                {
                    nodesPath.add(new LinkedList<Node>());
                    nodesPath.get(j).add(paths.get(i).get(j));
                }                              
                else if (!nodesPath.get(j).contains(paths.get(i).get(j)))
                {
                    nodesPath.get(j).add(paths.get(i).get(j));
                }
            }
        }
        
        return nodesPath;
    }
    
    // </editor-fold>
}
